Micron Document
๐ŸŽ–๏ธGitะฏั€ะฐ๐ŸŽ–๏ธ

Node / meshtastic / Meshtastic-Android / files / core / data / src / commonMain / kotlin / org / meshtastic / core / data / manager / XModemManagerImpl.kt

Displaying Raw โ€ข Download

core/data/src/commonMain/kotlin/org/meshtastic/core/data/manager/XModemManagerImpl.kt cdeb1ac532b587f5db718504b5d6093ab55a859c (cdeb1ac5) Text, 6.86 KB

T8b949e/*
* Copyright (c) 2025-2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
Tff7b72package T7ee787org.meshtastic.core.data.manager

Tff7b72import T7ee787co.touchlab.kermit.Logger
Tff7b72import T7ee787kotlinx.coroutines.channels.BufferOverflow
Tff7b72import T7ee787kotlinx.coroutines.flow.MutableSharedFlow
Tff7b72import T7ee787kotlinx.coroutines.flow.asSharedFlow
Tff7b72import T7ee787org.koin.core.annotation.Single
Tff7b72import T7ee787org.meshtastic.core.common.util.nowMillis
Tff7b72import T7ee787org.meshtastic.core.repository.PacketHandler
Tff7b72import T7ee787org.meshtastic.core.repository.XModemFile
Tff7b72import T7ee787org.meshtastic.core.repository.XModemManager
Tff7b72import T7ee787org.meshtastic.proto.ToRadio
Tff7b72import T7ee787org.meshtastic.proto.XModem
Tff7b72import T7ee787kotlin.concurrent.Volatile

T8b949e/**
* XModem-CRC receiver state machine.
*
* Protocol summary (device = sender, Android = receiver):
* - SOH / STX โ†’ data block with seq, CRC-CCITT-16, payload; reply ACK or NAK
* - EOT โ†’ end of transfer; reply ACK, emit assembled file
* - CAN โ†’ sender cancelled; reset state
*
* CRC algorithm: CRC-CCITT (poly 0x1021, init 0x0000), same as the Meshtastic firmware.
*/
Tf0883e@Single
Tff7b72class T56d364XModemManagerImplTb4b4b4(Tff7b72private Tff7b72val Te6edf3packetHandlerTb4b4b4: Te6edf3PacketHandlerTb4b4b4) Tb4b4b4: Te6edf3XModemManager Tb4b4b4{

Tff7b72private Tff7b72val Te6edf3_fileTransferFlow Tff7b72=
Te6edf3MutableSharedFlowTff7b72<Te6edf3XModemFileTff7b72>Tb4b4b4(
Te6edf3replay Tff7b72= T79c0ff0Tb4b4b4,
Te6edf3extraBufferCapacity Tff7b72= T79c0ff4Tb4b4b4,
Te6edf3onBufferOverflow Tff7b72= Te6edf3BufferOverflowTb4b4b4.Te6edf3DROP_OLDESTTb4b4b4,
Tb4b4b4)
Tff7b72override Tff7b72val Te6edf3fileTransferFlow Tff7b72= Te6edf3_fileTransferFlowTb4b4b4.Te6edf3asSharedFlowTb4b4b4(Tb4b4b4)

T8b949e// --- mutable state ---
T8b949e// Thread-safety contract: [handleIncomingXModem] is called sequentially from
T8b949e// [FromRadioPacketHandlerImpl.handleFromRadio] on a single IO coroutine. The
T8b949e// [setTransferName] and [cancel] calls originate from UI/ViewModel coroutines
T8b949e// and are guarded by @Volatile for visibility. Concurrent block processing is
T8b949e// not possible because the firmware sends one XModem packet at a time and waits
T8b949e// for ACK/NAK before sending the next.
Tf0883e@Volatile Tff7b72private Tff7b72var Te6edf3transferName Tff7b72= Ta5d6ff"Ta5d6ff"

Tf0883e@Volatile Tff7b72private Tff7b72var Te6edf3expectedSeq Tff7b72= Te6edf3INITIAL_SEQ

Tf0883e@Volatile Tff7b72private Tff7b72var Te6edf3lastActivityMillis Tff7b72= T79c0ff0L
Tff7b72private Tff7b72val Te6edf3blocks Tff7b72= Te6edf3mutableListOfTff7b72<Te6edf3ByteArrayTff7b72>Tb4b4b4(Tb4b4b4)

Tff7b72override Tff7b72fun Td2a8ffsetTransferNameTb4b4b4(Te6edf3nameTb4b4b4: Tffa657StringTb4b4b4) Tb4b4b4{
Te6edf3transferName Tff7b72= Te6edf3name
Tb4b4b4}

Tff7b72override Tff7b72fun Td2a8ffhandleIncomingXModemTb4b4b4(Te6edf3packetTb4b4b4: Te6edf3XModemTb4b4b4) Tb4b4b4{
T8b949e// If blocks have accumulated but no activity for INACTIVITY_TIMEOUT_MS,
T8b949e// the previous transfer is stale (firmware crash, BLE disconnect, etc.).
Tff7b72if Tb4b4b4(Te6edf3blocksTb4b4b4.Te6edf3isNotEmptyTb4b4b4(Tb4b4b4) Tff7b72&Tff7b72& Te6edf3lastActivityMillis Tff7b72> T79c0ff0LTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3elapsed Tff7b72= Te6edf3nowMillis Tff7b72- Te6edf3lastActivityMillis
Tff7b72if Tb4b4b4(Te6edf3elapsed Tff7b72> Te6edf3INACTIVITY_TIMEOUT_MSTb4b4b4) Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3w Tb4b4b4{ Ta5d6ff"Ta5d6ffXModem: inactivity timeout (Tffd700${Te6edf3elapsedTffd700}Ta5d6ffms) โ€” resetting stale transferTa5d6ff" Tb4b4b4}
Te6edf3resetTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Te6edf3lastActivityMillis Tff7b72= Te6edf3nowMillis

Tff7b72when Tb4b4b4(Te6edf3packetTb4b4b4.Te6edf3controlTb4b4b4) Tb4b4b4{
Te6edf3XModemTb4b4b4.Te6edf3ControlTb4b4b4.Te6edf3SOHTb4b4b4,
Te6edf3XModemTb4b4b4.Te6edf3ControlTb4b4b4.Te6edf3STXTb4b4b4,
Tff7b72-Tff7b72> Te6edf3handleDataBlockTb4b4b4(Te6edf3packetTb4b4b4)
Te6edf3XModemTb4b4b4.Te6edf3ControlTb4b4b4.Te6edf3EOT Tff7b72-Tff7b72> Te6edf3handleEotTb4b4b4(Tb4b4b4)
Te6edf3XModemTb4b4b4.Te6edf3ControlTb4b4b4.Te6edf3CAN Tff7b72-Tff7b72> Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3w Tb4b4b4{ Ta5d6ff"Ta5d6ffXModem: CAN received โ€” transfer cancelledTa5d6ff" Tb4b4b4}
Te6edf3resetTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tff7b72else Tff7b72-Tff7b72> Te6edf3LoggerTb4b4b4.Te6edf3w Tb4b4b4{ Ta5d6ff"Ta5d6ffXModem: unexpected control byte Tffd700${Te6edf3packetTb4b4b4.Te6edf3controlTffd700}Ta5d6ff, ignoringTa5d6ff" Tb4b4b4}
Tb4b4b4}
Tb4b4b4}

Tff7b72private Tff7b72fun Td2a8ffhandleDataBlockTb4b4b4(Te6edf3packetTb4b4b4: Te6edf3XModemTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3seq Tff7b72= Te6edf3packetTb4b4b4.Te6edf3seq Te6edf3and T79c0ff0Te6edf3xFF
Tff7b72val Te6edf3data Tff7b72= Te6edf3packetTb4b4b4.Te6edf3bufferTb4b4b4.Te6edf3toByteArrayTb4b4b4(Tb4b4b4)

Tff7b72if Tb4b4b4(Tff7b72!Te6edf3validateCrcTb4b4b4(Te6edf3dataTb4b4b4, Te6edf3packetTb4b4b4.Te6edf3crc16Tb4b4b4)Tb4b4b4) Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3w Tb4b4b4{ Ta5d6ff"Ta5d6ffXModem: CRC error on block Tffd700$Te6edf3seqTa5d6ff (expected seq=Tffd700$Te6edf3expectedSeqTa5d6ff) โ€” NAKTa5d6ff" Tb4b4b4}
Te6edf3sendControlTb4b4b4(Te6edf3XModemTb4b4b4.Te6edf3ControlTb4b4b4.Te6edf3NAKTb4b4b4)
Tff7b72return
Tb4b4b4}

Tff7b72when Tb4b4b4(Te6edf3seqTb4b4b4) Tb4b4b4{
Te6edf3expectedSeq Tff7b72-Tff7b72> Tb4b4b4{
Te6edf3blocksTb4b4b4.Te6edf3addTb4b4b4(Te6edf3dataTb4b4b4)
Te6edf3expectedSeq Tff7b72= Tb4b4b4(Te6edf3expectedSeq Tff7b72% Te6edf3MAX_SEQTb4b4b4) Tff7b72+ T79c0ff1
Te6edf3LoggerTb4b4b4.Te6edf3d Tb4b4b4{ Ta5d6ff"Ta5d6ffXModem: block Tffd700$Te6edf3seqTa5d6ff OK, total=Tffd700${Te6edf3blocksTb4b4b4.Te6edf3sizeTffd700}Ta5d6ff blocksTa5d6ff" Tb4b4b4}
Te6edf3sendControlTb4b4b4(Te6edf3XModemTb4b4b4.Te6edf3ControlTb4b4b4.Te6edf3ACKTb4b4b4)
Tb4b4b4}
T8b949e// Duplicate: sender did not receive our previous ACK; re-ACK without buffering again.
Tb4b4b4(Te6edf3expectedSeq Tff7b72- T79c0ff1 Tff7b72+ Te6edf3MAX_SEQ_PLUS_ONETb4b4b4) Tff7b72% Te6edf3MAX_SEQ_PLUS_ONE Tff7b72-Tff7b72> Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3d Tb4b4b4{ Ta5d6ff"Ta5d6ffXModem: duplicate block Tffd700$Te6edf3seqTa5d6ff โ€” re-ACKTa5d6ff" Tb4b4b4}
Te6edf3sendControlTb4b4b4(Te6edf3XModemTb4b4b4.Te6edf3ControlTb4b4b4.Te6edf3ACKTb4b4b4)
Tb4b4b4}
Tff7b72else Tff7b72-Tff7b72> Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3w Tb4b4b4{ Ta5d6ff"Ta5d6ffXModem: unexpected seq Tffd700$Te6edf3seqTa5d6ff (expected Tffd700$Te6edf3expectedSeqTa5d6ff) โ€” NAKTa5d6ff" Tb4b4b4}
Te6edf3sendControlTb4b4b4(Te6edf3XModemTb4b4b4.Te6edf3ControlTb4b4b4.Te6edf3NAKTb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}

Tff7b72private Tff7b72fun Td2a8ffhandleEotTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3i Tb4b4b4{ Ta5d6ff"Ta5d6ffXModem: EOT โ€” transfer complete (Tffd700${Te6edf3blocksTb4b4b4.Te6edf3sizeTffd700}Ta5d6ff blocks, name='Tffd700$Te6edf3transferNameTa5d6ff')Ta5d6ff" Tb4b4b4}
Te6edf3sendControlTb4b4b4(Te6edf3XModemTb4b4b4.Te6edf3ControlTb4b4b4.Te6edf3ACKTb4b4b4)

Tff7b72val Te6edf3raw Tff7b72= Te6edf3blocksTb4b4b4.Te6edf3foldTb4b4b4(Te6edf3ByteArrayTb4b4b4(T79c0ff0Tb4b4b4)Tb4b4b4) Tb4b4b4{ Te6edf3accTb4b4b4, Te6edf3block Tff7b72-Tff7b72> Te6edf3acc Tff7b72+ Te6edf3block Tb4b4b4}
T8b949e// Strip trailing CTRL-Z padding that XModem senders add to fill the last block.
Tff7b72var Te6edf3end Tff7b72= Te6edf3rawTb4b4b4.Te6edf3size
Tff7b72while Tb4b4b4(Te6edf3end Tff7b72> T79c0ff0 Tff7b72&Tff7b72& Te6edf3rawTff7b72[Te6edf3end Tff7b72- T79c0ff1Tff7b72] Tff7b72=Tff7b72= Te6edf3CTRLZTb4b4b4) Te6edf3endTff7b72-Tff7b72-
Tff7b72val Te6edf3trimmed Tff7b72= Tff7b72if Tb4b4b4(Te6edf3end Tff7b72=Tff7b72= Te6edf3rawTb4b4b4.Te6edf3sizeTb4b4b4) Te6edf3raw Tff7b72else Te6edf3rawTb4b4b4.Te6edf3copyOfTb4b4b4(Te6edf3endTb4b4b4)
Te6edf3_fileTransferFlowTb4b4b4.Te6edf3tryEmitTb4b4b4(Te6edf3XModemFileTb4b4b4(Te6edf3name Tff7b72= Te6edf3transferNameTb4b4b4, Te6edf3data Tff7b72= Te6edf3trimmedTb4b4b4)Tb4b4b4)
Te6edf3resetTb4b4b4(Tb4b4b4)
Tb4b4b4}

Tff7b72override Tff7b72fun Td2a8ffcancelTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3i Tb4b4b4{ Ta5d6ff"Ta5d6ffXModem: cancelling transferTa5d6ff" Tb4b4b4}
Te6edf3sendControlTb4b4b4(Te6edf3XModemTb4b4b4.Te6edf3ControlTb4b4b4.Te6edf3CANTb4b4b4)
Te6edf3resetTb4b4b4(Tb4b4b4)
Tb4b4b4}

Tff7b72private Tff7b72fun Td2a8ffsendControlTb4b4b4(Te6edf3controlTb4b4b4: Te6edf3XModemTb4b4b4.Te6edf3ControlTb4b4b4) Tb4b4b4{
Te6edf3packetHandlerTb4b4b4.Te6edf3sendToRadioTb4b4b4(Te6edf3ToRadioTb4b4b4(Te6edf3xmodemPacket Tff7b72= Te6edf3XModemTb4b4b4(Te6edf3control Tff7b72= Te6edf3controlTb4b4b4)Tb4b4b4)Tb4b4b4)
Tb4b4b4}

Tff7b72private Tff7b72fun Td2a8ffresetTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3expectedSeq Tff7b72= Te6edf3INITIAL_SEQ
Te6edf3blocksTb4b4b4.Te6edf3clearTb4b4b4(Tb4b4b4)
Te6edf3transferName Tff7b72= Ta5d6ff"Ta5d6ff"
Te6edf3lastActivityMillis Tff7b72= T79c0ff0L
Tb4b4b4}

T8b949e// CRC-CCITT: polynomial 0x1021, initial value 0x0000 (XModem variant)
Tff7b72private Tff7b72fun Td2a8ffvalidateCrcTb4b4b4(Te6edf3dataTb4b4b4: Te6edf3ByteArrayTb4b4b4, Te6edf3expectedCrcTb4b4b4: Tffa657IntTb4b4b4)Tb4b4b4: Tffa657Boolean Tff7b72=
Te6edf3calculateCrc16Tb4b4b4(Te6edf3dataTb4b4b4) Tff7b72=Tff7b72= Tb4b4b4(Te6edf3expectedCrc Te6edf3and T79c0ff0Te6edf3xFFFFTb4b4b4)

Tff7b72private Tff7b72fun Td2a8ffcalculateCrc16Tb4b4b4(Te6edf3dataTb4b4b4: Te6edf3ByteArrayTb4b4b4)Tb4b4b4: Tffa657Int Tb4b4b4{
Tff7b72var Te6edf3crc Tff7b72= T79c0ff0
Tff7b72for Tb4b4b4(Te6edf3byte Tff7b72in Te6edf3dataTb4b4b4) Tb4b4b4{
Te6edf3crc Tff7b72= Te6edf3crc Te6edf3xor Tb4b4b4(Tb4b4b4(Te6edf3byteTb4b4b4.Te6edf3toIntTb4b4b4(Tb4b4b4) Te6edf3and T79c0ff0Te6edf3xFFTb4b4b4) Te6edf3shl T79c0ff8Tb4b4b4)
Te6edf3repeatTb4b4b4(Te6edf3BITS_PER_BYTETb4b4b4) Tb4b4b4{ Te6edf3crc Tff7b72= Tff7b72if Tb4b4b4(Te6edf3crc Te6edf3and T79c0ff0Te6edf3x8000 Tff7b72!Tff7b72= T79c0ff0Tb4b4b4) Tb4b4b4(Te6edf3crc Te6edf3shl T79c0ff1Tb4b4b4) Te6edf3xor Te6edf3CRC_POLY Tff7b72else Te6edf3crc Te6edf3shl T79c0ff1 Tb4b4b4}
Tb4b4b4}
Tff7b72return Te6edf3crc Te6edf3and T79c0ff0Te6edf3xFFFF
Tb4b4b4}

Tff7b72companion Tff7b72object Tb4b4b4{
Tff7b72private Tff7b72const Tff7b72val Te6edf3INITIAL_SEQ Tff7b72= T79c0ff1
Tff7b72private Tff7b72const Tff7b72val Te6edf3MAX_SEQ Tff7b72= T79c0ff2T79c0ff5T79c0ff5
Tff7b72private Tff7b72const Tff7b72val Te6edf3MAX_SEQ_PLUS_ONE Tff7b72= T79c0ff2T79c0ff5T79c0ff6
Tff7b72private Tff7b72const Tff7b72val Te6edf3CTRLZ Tff7b72= T79c0ff0Te6edf3x1ATb4b4b4.Te6edf3toByteTb4b4b4(Tb4b4b4)
Tff7b72private Tff7b72const Tff7b72val Te6edf3CRC_POLY Tff7b72= T79c0ff0Te6edf3x1021
Tff7b72private Tff7b72const Tff7b72val Te6edf3BITS_PER_BYTE Tff7b72= T79c0ff8
Tff7b72private Tff7b72const Tff7b72val Te6edf3INACTIVITY_TIMEOUT_MS Tff7b72= T79c0ff3T79c0ff0Te6edf3_000L
Tb4b4b4}
Tb4b4b4}

Served by rngit 1.5.0 - Generated in 0.08s